﻿/*
 * Сделано в SharpDevelop.
 * Пользователь: max
 * Дата: 09.09.2012
 * Время: 21:26
 * 
 * Для изменения этого шаблона используйте Сервис | Настройка | Кодирование | Правка стандартных заголовков.
 */
using System;
using System.Threading;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Text;
using Hammock;

namespace jellybird.core
{
	/// <summary>
	/// Description of TwUsersList.
	/// </summary>
	public class TwUsersCache
	{
		/*
		 * api v 1.1
		 * https://dev.twitter.com/docs/api/1.1/get/users/lookup
		 */
		
		private ConcurrentDictionary<long,TwUser> m_dict;
		private RestClient m_client;
		private Newtonsoft.Json.JsonSerializer m_serializer;
		
		#region singleton
		
		private TwUsersCache(TwCredentials credentials)
		{
			m_serializer=new Newtonsoft.Json.JsonSerializer();
			m_client=new RestClient()
			{
				Authority=Constants.API_TWITTER_COM,
				Credentials=new Hammock.Authentication.OAuth.OAuthCredentials()
				{
					ConsumerKey=credentials.ConsumerKey,
					ConsumerSecret=credentials.ConsumerSecret,
					SignatureMethod=Hammock.Authentication.OAuth.OAuthSignatureMethod.HmacSha1,
					Token=credentials.AccessToken,
					TokenSecret=credentials.AccessTokenSecret,
					Type=Hammock.Authentication.OAuth.OAuthType.AccessToken
				},
				Deserializer=new JsonDeserializerWrapper(),
				VersionPath=Constants.API_VERSION_1_1
			};
			m_dict=new ConcurrentDictionary<long, TwUser>();
		}
		
		public static TwUsersCache Get(TwCredentials credentials)
		{
			if(credentials==null)
				return null;
			
			TwUsersCache ret=m_instances.GetOrAdd
				(credentials,
				 c=>new TwUsersCache(c));
			return ret;
		}
		
		public static bool Clear(TwCredentials credentials)
		{
			TwUsersCache target=null;
			if(m_instances.TryGetValue(credentials,out target))
			{
				target.m_dict.Clear();
				return true;
			}
			return false;
		}
		
		public static void ClearAll()
		{
			foreach(var ones in m_instances)
			{
				ones.Value.m_dict.Clear();
			}
		}
		
		public static bool Remove(TwCredentials credentials)
		{
			TwUsersCache removing=null;
			return m_instances.TryRemove(credentials,out removing);
		}
		
		public static void RemoveAll()
		{
			m_instances.Clear();
		}
		
		private static ConcurrentDictionary<TwCredentials,TwUsersCache> m_instances=new ConcurrentDictionary<TwCredentials, TwUsersCache>();
		
		#endregion
		
		#region events
		public event EventHandler<TwObjectReceivedEventArgs<TwUser>> UsersReceived;
		
		private void On_received(TwObjectReceivedEventArgs<TwUser> arg)
		{
			if(UsersReceived!=null)
			{
				UsersReceived.Invoke(this,arg);
			}
		}
		#endregion
		
		#region update engine
		private TwUser add_factory(long id)
		{
			RestRequest request=new RestRequest()
			{
				Method=Hammock.Web.WebMethod.Get,
				Path=Constants.USERS+'/'+Constants.SHOW
			};
			request.AddParameter(Constants.USER_ID,id.ToString());
			
			RestResponse<TwUser> resp=m_client.Request<TwUser>(request);
			if(resp.ContentEntity!=null)
			{
				return resp.ContentEntity;
			}
			else
			{
				TwUser empty=new TwUser()
				{
					Id=id
				};
				//хоть так
				//чтобы что нибудь вернуть
				return empty;
			}
		}
		
		/*
		private void response_proc_bulk_update(RestRequest request, RestResponse response, object state)
		{
			#if DEBUG
			if(response!=null)
				Helper.OnDebugMessage
					(string.Format("Receive response from '{0}', Status={1}",response.ResponseUri,response.StatusCode));
			#endif
			
			State_object state_object=state as State_object;
			object user_state=state_object.User_state;
			EventWaitHandle wait_handle=state_object.Wait_handle;
			
			try
			{
				if(response!=null)
				{
					if(response.StatusCode==System.Net.HttpStatusCode.OK)
					{
						List<TwUser> received=new List<TwUser>();
						if(response.ContentStream!=null)
						{
							#if DEBUG
							Helper.OnDebugMessage
								(string.Format("Read from response data. Length={0}.",response.ContentLength));
							#endif
							StreamReader reader=new StreamReader(response.ContentStream);
							m_serializer.Populate(reader,received);
							foreach(TwUser tw in received)
							{
								tw.Client=m_client;
								//а то там будет ссылка на пустой клиент
							}
							#if DEBUG
							Helper.OnDebugMessage
								(string.Format("Reads {0} user objects from response data.",received.Count));
							#endif
							add_bulk(received);
							On_received(new TwObjectReceivedEventArgs<TwUser>(received,user_state));
						}//content stream != null
						else
						{
							//response stream null
							On_received
								(new TwObjectReceivedEventArgs<TwUser>
								 (new NullReferenceException("No response data."),
								  user_state));
						}
					}//status = OK
					else
					{
						On_received
							(new TwObjectReceivedEventArgs<TwUser>(response.InnerException,user_state));
					}
				}//response!=null
				else
				{
					On_received
						(new TwObjectReceivedEventArgs<TwUser>
						 (new NullReferenceException("No response."),
						  user_state));
				}
			}//основной try
			catch(Exception ex)
			{
				On_received
					(new TwObjectReceivedEventArgs<TwUser>
					 (ex,
					  user_state));
			}
			finally
			{
				if(response!=null)
					response.Dispose();
				
				wait_handle.Set();
			}
		}
		 */
		
		private void add_bulk(List<TwUser> list_tmp)
		{
			foreach(var ones in list_tmp)
			{
				m_dict.AddOrUpdate
					(ones.Id,
					 ones,
					 (id,existing)=>ones);
			}
		}
		
		/*
		private IAsyncResult begin_update_bulk_intern
			(IEnumerable<long> ids,EventWaitHandle wait_handle,object user_state)
		{
			RestRequest request=create_bulk_request(ids);
			
			State_object state_object=new TwUsersCache.State_object()
			{
				User_state=user_state,
				Wait_handle=wait_handle
			};
			
			IAsyncResult ret=m_client.BeginRequest
				(request,
				 new RestCallback(response_proc_bulk_update),
				 state_object);
			return ret;
		}
		 */
		
		private IEnumerable<RestRequest> create_bulk_request
			(IEnumerable<long> ids)
		{
			foreach(string id_set in create_comma_separated_ids(enum_absent_ids_and_duplicates(ids)))
			{
				if(!string.IsNullOrWhiteSpace(id_set))
				{
					RestRequest request=new RestRequest()
					{
						Method=Hammock.Web.WebMethod.Post,
						Path=Constants.USERS+'/'+Constants.LOOKUP,
						
					};
					request.AddParameter(Constants.USER_ID,id_set);
					yield return request;
				}
			}
		}
		
		private void update_bulk_sync(IEnumerable<long> ids)
		{
			foreach(RestRequest request in create_bulk_request(ids))
			{
				RestResponse<List<TwUser>> rest_response=null;
				List<TwUser> received;
				try
				{
					#if DEBUG
					Helper.OnDebugMessage
						(string.Format
						 ("Executing request {0}",
						  request.Path));
					#endif
					
					rest_response=m_client.Request<List<TwUser>>(request);
					received=rest_response.ContentEntity;
					
					#if DEBUG
					Helper.OnDebugMessage(string.Format
					                      ("Cache lookups users '{0}' status={1}. Recieved {2} object(s).",rest_response.RequestUri,rest_response.StatusCode,received.Count));
					#endif
				}
				catch(Exception ex)
				{
					On_received
						(new TwObjectReceivedEventArgs<TwUser>
						 (ex,
						  null));
					throw ex;
				}
				finally
				{
					if(rest_response!=null)
						rest_response.Dispose();
				}
				
				
				add_bulk(received);
				On_received
					(new TwObjectReceivedEventArgs<TwUser>(received,null));
			}
		}
		
		/*
		private class State_object
		{
			public object User_state;
			public EventWaitHandle Wait_handle;
		}
		 */
		
		private IEnumerable<long> enum_absent_ids_and_duplicates(IEnumerable<long> ids)
		{
			SortedSet<long> temp_set=new SortedSet<long>();
			
			foreach(long id in ids)
			{
				if(temp_set.Contains(id))
				{
					continue;
				}
				if(m_dict.ContainsKey(id))
				{
					continue;
				}
				temp_set.Add(id);
				yield return id;
			}
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="ids"></param>
		/// <returns>returns array -> each element less tan 100 ids</returns>
		private IEnumerable<string> create_comma_separated_ids(IEnumerable<long> ids)
		{
			StringBuilder ret_builder=new StringBuilder();
			int id_count=0;
			
			foreach(long id in ids)
			{
				if(id_count==100)
				{
					//достигли макимума, вернём одну строку
					//последний разделитель - долой
					if(ret_builder.Length>0)
					{
						ret_builder.Remove(ret_builder.Length-1,1);
					}
					yield return ret_builder.ToString();
					ret_builder.Clear();
					id_count=0;
				}
				
				ret_builder.Append(id);
				ret_builder.Append(Constants.IDS_DELIMITER);
				id_count++;
			}
			
			//последний разделитель - долой
			if(ret_builder.Length>0)
			{
				ret_builder.Remove(ret_builder.Length-1,1);
			}
			yield return ret_builder.ToString();
		}
		
		
		
		#endregion
		
		/*
		/// <summary>
		/// 
		/// </summary>
		/// <param name="user_ids">less then 100</param>
		/// <param name="user_state"></param>
		/// <returns>Wait handle signaled after update completed, mustbe closed</returns>
		public WaitHandle BeginEnsureCache
			(IEnumerable<long> user_ids,
			 object user_state)
		{
			EventWaitHandle ret=new EventWaitHandle(false,System.Threading.EventResetMode.ManualReset);
			
			IAsyncResult res=begin_update_bulk_intern
				(user_ids,ret,user_state);
			
			return ret;
		}
		 */
		
		/// <summary>
		/// Sync method
		/// </summary>
		/// <param name="user_ids">any id now</param>
		public void EnsureCache
			(IEnumerable<long> user_ids)
		{
			update_bulk_sync(user_ids);
		}
		
		public void EnsureCache
			(TwUser user)
		{
			m_dict[user.Id]=user;
		}
		
		public TwUser this[long id]
		{
			get
			{
				return m_dict.GetOrAdd
					(id,
					 (key)=>add_factory(key));
			}
			set
			{
				m_dict.AddOrUpdate
					(id,
					 value,
					 (key,existing)=>value);
			}
		}
		
		public int Count
		{
			get{return m_dict.Count;}
		}
	}
}
